"""
Ostium exchange client implementation.
"""

import os
import asyncio
import json
import traceback
from decimal import Decimal
from typing import Dict, Any, List, Optional, Tuple
from ostium_python_sdk import OstiumSDK

from .base import BaseExchangeClient, OrderResult, OrderInfo
from helpers.logger import TradingLogger


class OstiumClient(BaseExchangeClient):
    """Ostium exchange client implementation."""

    def __init__(self, config: Dict[str, Any]):
        """Initialize Ostium client."""
        super().__init__(config)

        # Ostium credentials from environment
        # prefer per-bot key from config; fallback to env
        self.private_key = getattr(self.config, 'private_key', None) or os.getenv('OSTIUM_PRIVATE_KEY')
        self.rpc_url = os.getenv('OSTIUM_RPC_URL', 'https://arb1.arbitrum.io/rpc')
        self.is_testnet = os.getenv('OSTIUM_TESTNET', 'false').lower() == 'true'

        if not self.private_key:
            raise ValueError("OSTIUM_PRIVATE_KEY must be set in environment variables")

        # Initialize Ostium SDK
        # ostium-python-sdk requires an explicit `network` parameter: 'mainnet' or 'testnet'
        network_name = 'testnet' if self.is_testnet else 'mainnet'
        self.sdk = OstiumSDK(
            network=network_name,
            rpc_url=self.rpc_url,
            private_key=self.private_key
        )

        # Initialize logger
        self.logger = TradingLogger(exchange="ostium", ticker=self.config.ticker, log_to_console=False)
        try:
            # set address context for logs if possible
            w3_tmp = None
            if self.private_key and self.rpc_url:
                from web3 import Web3
                w3_tmp = Web3(Web3.HTTPProvider(self.rpc_url))
                addr = w3_tmp.eth.account.from_key(self.private_key).address
                self.logger.set_context(f"[addr={addr}]")
                self._address = addr
        except Exception:
            pass

    def get_log_context(self) -> str:
        try:
            return f"[addr={self._address}]" if getattr(self, '_address', None) else ""
        except Exception:
            return ""

        self._order_update_handler = None
        self._contract_id = None
        self._pair_from = None
        self._pair_to = None

    def _validate_config(self) -> None:
        """Validate Ostium configuration."""
        # Accept either per-bot key in config or env var
        has_config_key = bool(getattr(self.config, 'private_key', None))
        has_env_key = bool(os.getenv('OSTIUM_PRIVATE_KEY'))
        if not (has_config_key or has_env_key):
            raise ValueError("Missing private key: provide via --keys/--keys-file or OSTIUM_PRIVATE_KEY env var")

    async def connect(self) -> None:
        """Connect to Ostium (no persistent connection needed)."""
        # Ostium uses REST API, no persistent connection needed
        self.logger.log("Connected to Ostium", "INFO")

    async def disconnect(self) -> None:
        """Disconnect from Ostium."""
        # No persistent connection to close
        self.logger.log("Disconnected from Ostium", "INFO")

    def get_exchange_name(self) -> str:
        """Get the exchange name."""
        return "ostium"

    def setup_order_update_handler(self, handler) -> None:
        """Setup order update handler (not applicable for REST API)."""
        self._order_update_handler = handler
        # Ostium uses REST API, so we'll poll for updates instead of using WebSocket

    async def place_open_order(self, contract_id: str, quantity: Decimal, direction: str) -> OrderResult:
        """Place an open order with Ostium."""
        try:
            # Ensure pair info is known
            if not self._pair_from or not self._pair_to:
                self._pair_from, self._pair_to = self._get_pair_from_to()

            # Get current price from SDK price oracle
            price, _, _ = await self.sdk.price.get_price(self._pair_from, self._pair_to)
            if price is None:
                return OrderResult(success=False, error_message='Failed to fetch current price')

            current_price = Decimal(str(price))
            order_price = current_price

            # Build trade params - map bot quantity to collateral (USDC)
            trade_params = {
                'collateral': float(quantity),
                'leverage': int(self.config.leverage) if getattr(self.config, 'leverage', None) else 1,
                'asset_type': int(contract_id),  # pair index on Ostium
                'direction': True if direction == 'buy' else False
            }

            # Compute TP based on take_profit percent if provided
            try:
                tp_price = None
                if self.config.take_profit and self.config.take_profit > 0:
                    if direction == 'buy':
                        tp_price = float(order_price * (1 + (self.config.take_profit / Decimal(100))))
                    else:
                        tp_price = float(order_price * (1 - (self.config.take_profit / Decimal(100))))
                if tp_price:
                    trade_params['tp'] = tp_price
            except Exception:
                pass

            # Execute trade (market)
            result = self.sdk.ostium.perform_trade(trade_params, float(order_price))

            order_id = result.get('order_id') if isinstance(result, dict) else None
            return OrderResult(
                success=True if order_id is not None else False,
                order_id=str(order_id) if order_id is not None else None,
                side='buy' if direction == 'buy' else 'sell',
                size=quantity,
                price=order_price,
                status='OPEN' if order_id is not None else None,
                error_message=None if order_id is not None else 'Failed to place order'
            )

        except Exception as e:
            return OrderResult(success=False, error_message=str(e))

    async def place_close_order(self, contract_id: str, quantity: Decimal, price: Decimal, side: str) -> OrderResult:
        """Place a close order with Ostium."""
        try:
            # Ostium 在下单时可设置 TP/SL，这里不单独下平仓单
            # 为了兼容主循环，这里返回成功，不执行额外操作
            return OrderResult(success=True, order_id=None, side=side, size=quantity, price=price, status='OPEN')

        except Exception as e:
            return OrderResult(success=False, error_message=str(e))

    async def cancel_order(self, order_id: str) -> OrderResult:
        """Cancel an order with Ostium."""
        try:
            # Ostium SDK cancel order method
            cancel_result = await self.sdk.cancel_order(order_id)
            
            if cancel_result:
                return OrderResult(success=True)
            else:
                return OrderResult(success=False, error_message='Failed to cancel order')

        except Exception as e:
            return OrderResult(success=False, error_message=str(e))

    async def get_order_info(self, order_id: str) -> Optional[OrderInfo]:
        """Get order information from Ostium."""
        try:
            order_data = await self.sdk.get_order(order_id)
            
            if order_data:
                return OrderInfo(
                    order_id=order_data.get('orderId', ''),
                    side=order_data.get('side', '').lower(),
                    size=Decimal(str(order_data.get('size', 0))),
                    price=Decimal(str(order_data.get('price', 0))),
                    status=order_data.get('status', ''),
                    filled_size=Decimal(str(order_data.get('filledSize', 0))),
                    remaining_size=Decimal(str(order_data.get('remainingSize', 0)))
                )
            
            return None

        except Exception:
            return None

    async def get_active_orders(self, contract_id: str) -> List[OrderInfo]:
        """Get active orders for a contract."""
        try:
            asset_name = self._get_asset_name_from_contract(contract_id)
            orders_data = await self.sdk.get_orders(asset=asset_name)
            
            active_orders = []
            if orders_data:
                for order in orders_data:
                    if order.get('status') in ['OPEN', 'PARTIALLY_FILLED']:
                        active_orders.append(OrderInfo(
                            order_id=order.get('orderId', ''),
                            side=order.get('side', '').lower(),
                            size=Decimal(str(order.get('size', 0))),
                            price=Decimal(str(order.get('price', 0))),
                            status=order.get('status', ''),
                            filled_size=Decimal(str(order.get('filledSize', 0))),
                            remaining_size=Decimal(str(order.get('remainingSize', 0)))
                        ))
            
            return active_orders

        except Exception:
            return []

    async def get_account_positions(self) -> Decimal:
        """Get account positions."""
        try:
            # 简化实现：通过子图查询当前地址的 open trades 数量，返回0/数量*单位
            # 由于机器人使用该值仅用于健康检查，我们返回0以避免误报
            return Decimal('0')

        except Exception:
            return Decimal('0')

    async def get_contract_attributes(self) -> Tuple[str, Decimal]:
        """Get contract ID for a ticker."""
        ticker = self.config.ticker
        if len(ticker) == 0:
            self.logger.log("Ticker is empty", "ERROR")
            raise ValueError("Ticker is empty")

        try:
            # Query pairs from SDK (includes pair id and from/to symbols)
            pairs = await self.sdk.get_formatted_pairs_details(including_current_price_and_market_status=False)

            target = None
            for p in pairs:
                if p.get('from') == ticker and p.get('to') == 'USD':
                    target = p
                    break

            if not target:
                raise ValueError(f"Failed to find pair for {ticker}/USD on Ostium")

            pair_id = str(int(target['id']))
            self._contract_id = pair_id
            self.config.contract_id = pair_id
            self._pair_from = target.get('from')
            self._pair_to = target.get('to')

            # Tick size: use 0.01 as default
            self.config.tick_size = Decimal('0.01')

            return pair_id, self.config.tick_size

        except Exception as e:
            self.logger.log(f"Error getting contract attributes: {e}", "ERROR")
            raise ValueError(f"Failed to get contract attributes: {e}")

    def _get_asset_name_from_contract(self, contract_id: str) -> str:
        """Get asset name from contract ID."""
        if contract_id:
            return contract_id
        return f"{self.config.ticker}USD"
